package sync
{
	import actions.ActionType;
	import actions.AddAction;
	import actions.DeleteAction;
	import actions.MoveAction;
	import actions.RequesBoardAction;
	import actions.ResizeAction;
	import actions.SyncBoardAction;
	import actions.WBAction;
	
	import events.BoardSyncedEvent;
	import events.ObjectEvent;
	import events.P2PMessageEvent;
	
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.utils.Dictionary;
	import flash.utils.getDefinitionByName;
	import flash.utils.setTimeout;
	
	import model.CircleObject;
	import model.ObjectType;
	import model.RectangleObject;
	import model.WBObject;
	import model.WhiteBoardModel;
	
	import mx.core.ClassFactory;
	import mx.utils.object_proxy;

	public class WhiteBoardSynchronizer extends EventDispatcher
	{
		
		private var _receiver:P2PMessageReceiver;
		private var _sender:P2PMessageSender;
		// a hash table of object synchronizers
		// key - uid of object
		// object - ObjectSynchronizer
		private var _objectsSyncs:Dictionary;
		
		//a queue of future actions
		private var _actionQueue:Array;
		//a list of already applied actions
		private var _historyList:Array;
		//shows if we are currently synching the white-board action list
		private var _isInSyncProgress:Boolean;
		
		private var _model:WhiteBoardModel;
		private var _view:WhiteBoardView;
		/**
		 * This class is reponsible for synchronizing the model between peers
		 */
		public function WhiteBoardSynchronizer()
		{
//			_receiver = new P2PMessageReceiver();
//			_sender = new P2PMessageSender();
			_actionQueue = [];
			_historyList = [];
			_isInSyncProgress = false;
		}
		
		public function get view():WhiteBoardView
		{
			return _view;
		}

		public function set view(value:WhiteBoardView):void
		{
			_view = value;
		}

		public function get model():WhiteBoardModel
		{
			return _model;
		}

		public function set model(value:WhiteBoardModel):void
		{
			_model = value;
		}

		public function get sender():P2PMessageSender
		{
			return _sender;
		}
		
		public function set sender(value:P2PMessageSender):void
		{
			_sender = value;
		}
		
		public function get receiver():P2PMessageReceiver
		{
			return _receiver;
		}
		
		public function set receiver(value:P2PMessageReceiver):void
		{
			if(_receiver)
			{
				_receiver.removeEventListener(P2PMessageEvent.MESSAGE_RECEIVED, messageReceived);
			}
			_receiver = value;
			
			if(_receiver)
			{
				_receiver.addEventListener(P2PMessageEvent.MESSAGE_RECEIVED, messageReceived, false, 0 ,true);
			}
			
		}
		
		protected function messageReceived(event:P2PMessageEvent):void
		{
			//assume that the message holds the action
			if(event.objectToSend.hasOwnProperty("className"))
			{
				var className:String = event.objectToSend.className;
				var classToInstantiate : Class = getDefinitionByName(className) as Class;
				var myClassFactory : ClassFactory = new ClassFactory(classToInstantiate);
				myClassFactory.properties = event.objectToSend;
				var myObjectInstance : * = myClassFactory.newInstance();

				executeAction(myObjectInstance);
			}
		}
		
		public function addAction(action:WBAction):void
		{
			_actionQueue.push(action);
			processNextAction();
		}
		
		/**
		 * This method is reponsible for processing the next action if there is no current sync process
		 * and call itself later if currently in sync progress
		 */
		private function processNextAction():void
		{
			if(!_isInSyncProgress)
			{
				if(_actionQueue.length > 0)
				{
					_isInSyncProgress = true;
					//start syncing for next future action
					var newAction:WBAction = _actionQueue.pop();
					executeAction(newAction);
					_historyList.push(newAction);
					_isInSyncProgress = false;
					//post action
					//XXX Do not resend peer action
					_sender.sendObject(newAction);
				}
				
			}
			else
			{
				setTimeout(processNextAction,100);
			}
		}
		
		private function executeAction(action:WBAction):void
		{
			switch(action.actionType)
			{
				case ActionType.ADD_ACTION:
					addObjectToBoard(action as AddAction);
					break;
				case ActionType.MOVE_ACTION:
					moveObjectOnBoard(action as MoveAction);
					break;
				case ActionType.DELETE_ACTION:
					deleteObjectFromBoard(action as DeleteAction);
					break;
				case ActionType.RESIZE_ACTION:
					resizeObjectOnBoard(action as ResizeAction);
					break;
				case ActionType.SYNC_BOARD_ACTION:
					syncWholeBoard(action as SyncBoardAction);
					break;
				case ActionType.REQUEST_BOARD_ACTION:
					requestBoard(action as RequesBoardAction);
					break;
			}
		}
		
		private function requestBoard(action:RequesBoardAction):void
		{
			var syncAction:SyncBoardAction = new SyncBoardAction();
			syncAction.objects = _model.listOfObjects.source;
			syncAction.callbackPeerId = action.callbackPeerId;
			_sender.sendObject(syncAction);
		}
		
		//sync the whole board - used in initial synchronization upon joining
		private function syncWholeBoard(action:SyncBoardAction):void
		{
			var objects:Array = getArrayOfRealObjects(action.objects);
			_model.syncBoard(objects);
			_view.syncBoard(objects);
			dispatchEvent(new BoardSyncedEvent(BoardSyncedEvent.BOARD_SYNC));
		}
		private function getArrayOfRealObjects(objectsArr:Array):Array
		{
			var result:Array = [];
			
			for(var i:int = 0; i < objectsArr.length; i++)
			{
				if((objectsArr[i]).hasOwnProperty("className"))
				{
					var className:String = (objectsArr[i]).className;
					var classToInstantiate : Class = getDefinitionByName(className) as Class;
					var myClassFactory : ClassFactory = new ClassFactory(classToInstantiate);
					myClassFactory.properties = (objectsArr[i]);
					var myObjectInstance : * = myClassFactory.newInstance();
					
					result.push(myObjectInstance);
				}
				else
				{
					result.push((objectsArr[i]));
				}
			}
			
			
			return result;
		}
		
		private function resizeObjectOnBoard(action:ResizeAction):void
		{
			var object:WBObject = _model.getObjectByUID(action.uid);
			if(object)
			{
				_model.resizeObject(object, action.deltaW, action.deltaH);
				_view.resizeObject(object, action.deltaW, action.deltaH);
				
			}
		}
		
		private function deleteObjectFromBoard(action:DeleteAction):void
		{
			var object:WBObject = _model.getObjectByUID(action.uid);
			if(object)
			{
				_model.removeObject(object);
				_view.removeObject(object);
			}
		}
		
		private function moveObjectOnBoard(action:MoveAction):void
		{
			//nothing to do here
			var object:WBObject = _model.getObjectByUID(action.uid);
			if(object)
			{
				object.xPos += action.deltaX;
				object.yPos += action.deltaY;
			}
		}

		private function addObjectToBoard(action:AddAction):void
		{
			var object:WBObject = null;
			switch(action.typeOfObject)
			{
				case ObjectType.CIRCLE_OBJECT:
					object = addCircleObject();
					break;
				case ObjectType.RECTANGLE_OBJECT:
					object = addRectangleObject();
					break;
			}
			if(object != null)
			{
				if(action.uid != null)
				{
					//then the object is not new and we need to add this already existing uid
					object.uid = action.uid;
				}
				else
				{
					action.uid = object.uid;
				}
				object.addEventListener(ObjectEvent.OBJECT_SELECTED, onObjectSelected, false, 0, true);
				_model.addObject(object);
				_view.addObject(object, action.xPos, action.yPos);
			}
		}
		
		/**
		 * Creates and adds to the collection of objects a new rectangle object 
		 */
		private function addCircleObject():WBObject
		{
			var circle:CircleObject = new CircleObject();
			circle.width = 100;
			circle.height = 100;
			
			return circle;
		}
		
		/**
		 * Creates and adds to the collection of objects a new rectangle object 
		 */
		private function addRectangleObject():WBObject
		{
			var rect:RectangleObject = new RectangleObject();
			rect.width = 100;
			rect.height = 100;
			
			return rect;
		}
		
		private function onObjectSelected(event:ObjectEvent):void
		{
			for each (var obj:WBObject in _model.listOfObjects)
			{
				if(obj != event.target)
				{
					obj.selected = false;
				}
			}
		}
	}
}